package com.ht.guice.jooq;

import com.google.inject.Inject;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

class TransactionalMethodInterceptor implements MethodInterceptor {

    @Inject
    private TxDataSourceConnectionProvider transactionManager;

    @Transactional
    private static class Internal {}

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {


        Transactional transactional = readTransactionMetadata(invocation);

        if (transactionManager.isOpened()) {// tx active
            //return invocation.proceed();
            try {
                Object result = invocation.proceed();
                transactionManager.commit();

                return result;
            } catch (Exception e) {
                if (rollbackIfNecessary(transactional, e)) {
                    transactionManager.commit();
                }

                throw e;
            }
        }

        transactionManager.open();
        try {
            Object result = invocation.proceed();
            transactionManager.commit();
            transactionManager.close();

            return result;
        } catch (Exception e) {
            if (rollbackIfNecessary(transactional, e)) {
                transactionManager.commit();
            }
            transactionManager.close();

            throw e;
        }
    }

    private Transactional readTransactionMetadata(MethodInvocation methodInvocation) {
        Transactional transactional;
        Method method = methodInvocation.getMethod();
        Class<?> targetClass = methodInvocation.getThis().getClass();

        transactional = method.getAnnotation(Transactional.class);
        if (null == transactional) {
            // If none on method, try the class.
            transactional = targetClass.getAnnotation(Transactional.class);
        }
        if (null == transactional) {
            // If there is no transactional annotation present, use the default
            transactional = Internal.class.getAnnotation(Transactional.class);
        }

        return transactional;
    }

    /**
     * Returns True if rollback DID NOT HAPPEN (i.e. if commit should continue).
     *
     * @param transactional The metadata annotaiton of the method
     * @param e The exception to test for rollback
     */
    private boolean rollbackIfNecessary(Transactional transactional, Exception e) {
        boolean commit = true;

        //check rollback clauses
        for (Class<? extends Exception> rollBackOn : transactional.rollbackOn()) {

            //if one matched, try to perform a rollback
            if (rollBackOn.isInstance(e)) {
                commit = false;

                //check ignore clauses (supercedes rollback clause)
                for (Class<? extends Exception> exceptOn : transactional.ignore()) {
                    //An exception to the rollback clause was found, DON'T rollback
                    // (i.e. commit and throw anyway)
                    if (exceptOn.isInstance(e)) {
                        commit = true;
                        break;
                    }
                }

                //rollback only if nothing matched the ignore check
                if (!commit) {
                    transactionManager.rollback();
                }
                //otherwise continue to commit

                break;
            }
        }

        return commit;
    }
}